﻿using System;
using System.Collections.Generic;
using JetBrains.Application;
using JetBrains.Application.Progress;
using JetBrains.Metadata.Reader.API;
using JetBrains.ProjectModel;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.ReSharper.UnitTestExplorer;
using JetBrains.Util.DataStructures;
using Objectware.Silverlight.Testing.Runner.Resharper.Elements;

namespace Objectware.Silverlight.Testing.Runner.Resharper
{
	public class FileExplorer : IRecursiveElementProcessor
	{
		private string AssemblyPath { get; set; }
		private Dictionary2<ITypeElement, ClassTestElement> Classes { get; set; }
		private UnitTestElementLocationConsumer Consumer { get; set; }
		private IFile File  { get; set; }
		private CheckForInterrupt Interrupted  { get; set; }
		private Dictionary2<IDeclaredElement, int> Orders { get; set; }
		private IProject Project { get; set; }
		private IUnitTestProvider Provider { get; set; }


        public FileExplorer(IUnitTestProvider provider,
							UnitTestElementLocationConsumer consumer,
							IFile file,
							CheckForInterrupt interrupted)
        {
            if (file == null)
                throw new ArgumentNullException("file");

            if (provider == null)
                throw new ArgumentNullException("provider");

			this.Classes = new Dictionary2<ITypeElement, ClassTestElement>();
			this.Orders = new Dictionary2<IDeclaredElement, int>();
            this.Consumer = consumer;
            this.Provider = provider;
            this.File = file;
            this.Interrupted = interrupted;

            this.Project = this.File.ProjectFile.GetProject();
            this.AssemblyPath = UnitTestManager.GetOutputAssemblyPath(this.Project).FullPath;
        }


		public bool InteriorShouldBeProcessed(IElement element)
		{
			if (element is ITypeMemberDeclaration)
			{
				return (element is ITypeDeclaration);
			}

			return true;
		}

		public void ProcessBeforeInterior(IElement element)
		{
			IDeclaration declaration = element as IDeclaration;
			if( null != declaration )
			{
				TestElement testElement = null;
				IDeclaredElement declaredElement = declaration.DeclaredElement;
				IClass testClass = declaredElement as IClass;

				if (testClass != null)
				{
					/*
					ITypeInfo typeInfo = TypeWrapper.Wrap(testClass);

					if (testClass.GetAccessRights() == AccessRights.PUBLIC &&
						TypeUtility.IsTestClass(typeInfo) &&
						!TypeUtility.HasRunWith(typeInfo))
						testElement = ProcessTestClass(testClass);
					 */
				}

				IMethod testMethod = declaredElement as IMethod;
				if (testMethod != null)
					testElement = ProcessTestMethod(testMethod) ?? testElement;

				if (testElement != null)
				{
					UnitTestElementDisposition disposition =
						new UnitTestElementDisposition(testElement,
													   this.File.ProjectFile,
													   declaration.GetNameRange(),
													   declaration.GetDocumentRange().TextRange);
					this.Consumer(disposition);
				}


			}
		}

		public void ProcessAfterInterior(IElement element) {}

		public bool ProcessingIsFinished
		{
			get
			{
				if( this.Interrupted() )
				{
					throw new ProcessCancelledException();
				}
				return false;
			}
		}

		TestElement ProcessTestClass(ITypeElement type)
		{
			TestElement testElement;
			ClassTestElement elementClass;

			if (this.Classes.ContainsKey(type))
			{
				elementClass = this.Classes[type];
			}
			else
			{
				elementClass = new ClassTestElement(this.Provider, this.Project, type.CLRName, this.AssemblyPath);
				this.Classes.Add(type, elementClass);
				this.Orders.Add(type, 0);
			}

			int order = 0;

			testElement = elementClass;
			AppendTests(elementClass, type.GetSuperTypes(), ref order);
			return testElement;
		}

        void AppendTests(ClassTestElement test,
                         IEnumerable<IDeclaredType> types,
                         ref int order)
        {
            foreach (IDeclaredType type in types)
            {
				
                ITypeElement typeElement = type.GetTypeElement();
                if (typeElement == null)
                {
                	continue;
                }

            	IClass @class = typeElement as IClass;
                if (@class == null)
                    continue;

				foreach( IMethod method in TypeUtility.GetTestMethods(typeElement))
				{
					new MethodTestElement(this.Provider, test, this.Project, typeElement.CLRName, method.ShortName, order++);
				}

                AppendTests(test, type.GetSuperTypes(), ref order);
            }
        }

		private TestElement ProcessTestMethod(IMethod method)
		{
			ITypeElement type = method.GetContainingType();
			if (type == null)
			{
				return null;
			}

			IClass @class = type as IClass;
			if (@class == null)
			{
				return null;
			}

			if( !Classes.ContainsKey(type) ) 
			{
				return null;
			}

			if (TypeUtility.IsTestMethod(method))
			{
				int order = Orders[type] + 1;
				Orders[type] = order;
				return new MethodTestElement(this.Provider, this.Classes[type], this.Project, type.CLRName, method.ShortName, order);
			}

			return null;
		}

	}
}
